﻿using System;
using System.Collections.Generic;
using System.Linq;
using log4net;
using Microsoft.SharePoint;
using SharePoint.Orm.Helpers;

namespace SharePoint.Orm.SiteBuilders {
	/// <summary>
	/// A builder for finding/adding content types.
	/// </summary>
	public class ContentTypeBuilder : BuilderBase<SPContentType> {
		private static readonly ILog _log = LogManager.GetLogger(typeof(ContentTypeBuilder));

		/// <summary>
		/// Required.  The display name of the content type as well as the internal name, static name, etc.  Just pretend there is
		/// no such thing as internal, static, etc.
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// Optional, default = null.  A description of the content type to be displayed to end users when the create new list items.
		/// </summary>
		public string Description { get; set; }

		/// <summary>
		/// Optional, default = empty which results is just a title field.  A list of site or site collection level fields 
		/// to add to the content type.
		/// </summary>
		public IList<FieldLinkBuilder> FieldLinks { get; set; }

		/// <summary>
		/// Required.  The group description to add the content type to.  This is static so you can set it once and instantiate the builder for each content type.
		/// </summary>
		public static string ContentTypeGroupName { get; set; }

		/// <summary>
		/// Optional, default = SPBuiltInContentTypeId.Item.
		/// </summary>
		public SPContentTypeId ParentContentTypeId { get; set; }

		/// <summary>
		/// Optional, default = false.  This field might be helpful if you want to hide the Title 
		/// field from the end user and auto-populate its= value in event handelers or something like that.
		/// </summary>
		public bool HideTitle { get; set; }

		/// <summary>
		/// Optional, default = false.  This field might be helpful if you want to hide the Title 
		/// field from the end user and auto-populate its value in event handelers or something like that.
		/// </summary>
		public bool ReadOnlyTitle { get; set; }

		/// <summary>
		/// Optional.  This isn't necessary most of the time, but it might come in useful if you inherit from another content type
		/// and want to order your fields relative to ones you didn't declare (e.g. you declare a "Middle Name", inherit from POC, and want
		/// your field to show up between "First Name" and "Last Name").  It's also convenient when you re-deploy and don't want the new
		/// field at the end.
		/// </summary>
		public List<ReorderInstruction> ReorderInstructions { get; set; }

		/// <summary>
		/// Optional.  The URL to redirect to when a user clicks an "Edit" button.  A typical value is something like "_layouts/Proj/MyEditForm.aspx".
		/// </summary>
		public string EditFormUrl { get; set; }

		/// <summary>
		/// Optional.  The URL to redirect to when a user clicks an "Add" button.  A typical value is something like "_layouts/Proj/MyAddForm.aspx".
		/// </summary>
		public string NewFormUrl { get; set; }
        /// <summary>
        /// Optional. The URL to redirect to when a user clicks a "Display" button. A typical value is something like "_layouts/Proj/MyDisplayForm.aspx".
        /// </summary>
        public string DisplayFormUrl { get; set; }

		/// <summary>
		/// Optional.  Allows you to do custom actions prior to calling .Update() on the content type.
		/// </summary>
		public Action<SPContentType> CustomAction { get; set; }

		/// <summary>
		/// Used to find/update a content type.  Typical usage involves just Name, Description, and FieldLinks and looks like this: 
		/// return new ContentTypeBuilder {
		/// 	Name = "MyContentType",
		///		Description = "A Description",
		///		FieldLinks = new List&lt;FieldLinkBuilder&gt; {
		///			new FieldLinkBuilder { Field = someField, Required = true }
		///		}
		/// }.FindAdd()
		/// </summary>
		public ContentTypeBuilder() {
			ParentContentTypeId = SPBuiltInContentTypeId.Item;
			HideTitle = false;
			ReadOnlyTitle = false;
			FieldLinks = new List<FieldLinkBuilder>();
			ReorderInstructions = new List<ReorderInstruction>();
		}

		protected class OrderableField {
			public string Name { get; private set; }
			public int Position { get; set; }
			private int _nextPosition;

			public OrderableField(string name, int position, int totalFields) {
				Name = name;
				Position = (position * totalFields) + 1;
				_nextPosition = Position;
			}

			public OrderableField(SPFieldLink fieldLink, int position, int totalFields)
				: this(fieldLink.Name, position, totalFields) {
			}

			public int GetNextPosition() {
				_nextPosition++;
				return _nextPosition;
			}
		}

		/// <summary>
		/// Reorders the fields of a content type using the ReorderInstructions provided at the field level.
		/// </summary>
		/// <param name="contentType"></param>
		public void ReorderFields(SPContentType contentType) {
			ReorderFields(contentType, ReorderInstructions);
		}

		/// <summary>
		/// Reorder the fields of a content type using the set of ReorderInstructions provided.
		/// </summary>
		/// <param name="contentType"></param>
		/// <param name="instructions"></param>
		public void ReorderFields(SPContentType contentType, IEnumerable<ReorderInstruction> instructions) {
			if (instructions == null || !instructions.Any()) return;

			int totalFields = contentType.FieldLinks.Count;
			IEnumerable<OrderableField> internalFields = contentType.FieldLinks
				.Cast<SPFieldLink>()
				.Select((fl, index) => new OrderableField(fl, index, totalFields))
				.ToList();

			string[] orderedInternalNames = GetOrderedInternalNames(internalFields, instructions);
			contentType.FieldLinks.Reorder(orderedInternalNames);
			contentType.Update(true);
		}

		protected string[] GetOrderedInternalNames(IEnumerable<OrderableField> internalFields, IEnumerable<ReorderInstruction> instructions) {
			int topPosition = 0 - internalFields.Count();

			foreach (ReorderInstruction rawInstruction in instructions) {
				ReorderInstruction instruction = rawInstruction;
				OrderableField fieldToMove = GetOrderableFieldByName(internalFields, instruction.Field);
				switch (instruction.Instruction) {
					case InstructionType.MoveAfter:
						if (instruction.RelativeTo == null) throw new SPOrmException("Relative to can not be null for MoveAfter operations.");
						OrderableField relativeToField = GetOrderableFieldByName(internalFields, instruction.RelativeTo);
						fieldToMove.Position = relativeToField.GetNextPosition();
						break;
					case InstructionType.MoveFirst:
						fieldToMove.Position = topPosition;
						topPosition++;
						break;
				}
			}

			return internalFields.OrderBy(f => f.Position).Select(f => f.Name).ToArray();
		}

		private static OrderableField GetOrderableFieldByName(IEnumerable<OrderableField> internalFields, string field) {
			OrderableField fieldToMove = internalFields.SingleOrDefault(f => f.Name == field);
			if (fieldToMove == null) throw new SPOrmException("Could not find Field '" + field + "'");
			return fieldToMove;
		}

		public override SPContentType FindAdd() {
			if (string.IsNullOrEmpty(ContentTypeGroupName)) throw new SPOrmException("ContentTypeGroupName is required.");
			if (Site == null) throw new SPOrmException("Site is required.");

			SPContentType contentType = SPWebHelper.FindOrAddContentType(Site, ContentTypeGroupName, Name, ParentContentTypeId);

			if (!string.IsNullOrEmpty(Description)) {
				contentType.Description = Description;
			}

			if (HideTitle || ReadOnlyTitle) {
				SPFieldLink fieldLink = contentType.FieldLinks["Title"];
				fieldLink.Hidden = HideTitle;
				fieldLink.ReadOnly = ReadOnlyTitle;
			}

			if (!string.IsNullOrEmpty(EditFormUrl)) {
				contentType.EditFormUrl = EditFormUrl;
			}

			if (!string.IsNullOrEmpty(NewFormUrl)) {
				contentType.NewFormUrl = NewFormUrl;
			}
            if(!string.IsNullOrEmpty(DisplayFormUrl)) {
                contentType.DisplayFormUrl = DisplayFormUrl;
            }
			if (!FieldLinks.Any()) {
				// not having any fieldlinks appears to make title hidden, so add it back in to make it visible
				SPField title = SPWebHelper.FindFieldByIdOrNameDefault(Site.Site.RootWeb, new Guid("fa564e0f-0c70-4ab9-b863-0177e6ddd247"), "");
				FieldLinks.Add(new FieldLinkBuilder { Field = title, Required = true });
			}

			foreach (FieldLinkBuilder fieldLinkBuilder in FieldLinks) {
				fieldLinkBuilder.ContentType = contentType;
				fieldLinkBuilder.FindAdd();
			}

			try {
				ReorderFields(contentType);
			} catch (Exception ex) {
				_log.Error(ex);
			}

			if (CustomAction != null) {
				CustomAction(contentType);
			}
			
			contentType.Update(true);
			return contentType;
		}
	}
}
